import {
  AxiosRequestConfig,
  AxiosPromise,
  Method,
  Axios as AxiosInterface,
  AxiosResponse,
  ResolvedFn,
  RejectedFn,
} from '../types'
import dispatchRequest, { tranformURL } from './dispatchRequest'
import InterceptorManager from './interceptorManager'
import mergeConfig from './mergeConfig'

interface interceptors {
  request: InterceptorManager<AxiosRequestConfig>
  response: InterceptorManager<AxiosResponse>
}

interface PromiseChain<T> {
  resolved: ResolvedFn<T> | ((config: AxiosRequestConfig) => AxiosResponse)
  rejected?: RejectedFn
}

export default class Axios implements AxiosInterface {
  defaultConfig: AxiosRequestConfig
  interceptors: interceptors

  constructor(initConfig: AxiosRequestConfig) {
    this.defaultConfig = initConfig
    this.interceptors = {
      request: new InterceptorManager<AxiosRequestConfig>(),
      response: new InterceptorManager<AxiosResponse>(),
    }
  }

  request(url: any, config?: any): AxiosPromise {
    if (typeof url === 'string') {
      if (!config) {
        config = {}
      }
      config.url = url
    } else {
      config = url
    }

    // 初始化拦截器链
    let chain: PromiseChain<any>[] = [
      {
        resolved: dispatchRequest,
        rejected: undefined,
      },
    ]

    // 合并请求配置文件
    config = mergeConfig(this.defaultConfig, config)

    // 遍历请求拦截器
    this.interceptors.request.foreach((interceptor) => {
      chain.unshift(interceptor)
    })

    // 遍历响应拦截器
    this.interceptors.response.foreach((interceptor) => {
      chain.push(interceptor)
    })

    // 初始化，加载config，然后再加载拦截器
    let promise: Promise<any> = Promise.resolve(config)
    while (chain.length) {
      const { rejected, resolved } = chain.shift()!
      // 这里就形成了一条Promise链
      promise = promise.then(resolved, rejected)
    }

    return promise
  }

  get(url: string, config?: AxiosRequestConfig): AxiosPromise {
    return this._requestMethodWithoutData('get', url, config)
  }

  delete(url: string, config?: AxiosRequestConfig): AxiosPromise {
    return this._requestMethodWithoutData('delete', url, config)
  }

  head(url: string, config?: AxiosRequestConfig): AxiosPromise {
    return this._requestMethodWithoutData('head', url, config)
  }

  options(url: string, config?: AxiosRequestConfig): AxiosPromise {
    return this._requestMethodWithoutData('options', url, config)
  }

  post(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise {
    return this._requestMethodWithData('post', url, data, config)
  }

  put(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise {
    return this._requestMethodWithData('put', url, data, config)
  }

  patch(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise {
    return this._requestMethodWithData('patch', url, data, config)
  }

  getUri(config: AxiosRequestConfig): string {
    config = mergeConfig(this.defaultConfig, config)
    return tranformURL(config)
  }

  _requestMethodWithoutData(
    method: Method,
    url: string,
    config?: AxiosRequestConfig
  ): AxiosPromise {
    // return dispatchRequest(
    //   Object.assign(config || {}, {
    //     url,
    //     method,
    //   })
    // )
    return this.request(
      Object.assign(config || {}, {
        url,
        method,
      })
    )
  }
  _requestMethodWithData(
    method: Method,
    url: string,
    data?: any,
    config?: AxiosRequestConfig
  ): AxiosPromise {
    // return dispatchRequest(
    //   Object.assign(config || {}, {
    //     url,
    //     method,
    //     data,
    //   })
    // )
    return this.request(
      Object.assign(config || {}, {
        url,
        method,
        data,
      })
    )
  }
}
